const EventEmitter = require('events');
import Logger from '../base/logger';
import io from './socket.io';
import { addEventListener, dispatchEvent, removeEventListener } from '../base/event';
import { IvcsError } from '../base/error';
import https from 'https';
const URL = require('url');
import { UrlParse } from '../base/urlParse';
import { ivcsErrorCodeEnum } from '../base/const';
const errorCode = {
	refreshTicketFailed: 100,
	resumeConnectionFailed: 101,
	ticketIsEmpty: 102,
	amsSocketIoDisconnected: 103,
	amsSocketIoDrop: 104,
	amsSocketIoReconnectFailed: 105,
};

const AmsEvent = {
	refreshTicketFailed: 'refreshTicketFailed',
	resumeConnectionFailed: 'resumeConnectionFailed',
	amsSocketIoDisconnected: 'ams_socket_disConnected',
	socketIoDrop: 'socketIoDrop',
	socketIoReconnectFailed: 'socketIoReconnectFailed',
	amsSocketIoReconnectFailed: 'amsSocketIoReconnectFailed',
};
const waitMessageResponseTimes = 60000; // 发送消息后等待响应的时长，默认60秒，比重连时间长即可。

const reconnectionAttempts = process.env.ivcsAgentMode != 1 ? 12 : 3; //1分钟

const pingTimeoutAttempts = 60; //ping-timeout attempt times
class AMSClient extends EventEmitter {
	constructor() {
		super();
		this.connected = false; //和ams的链接是否正常,不正常情况下无法发送ams-message
		this.isReconnected = false; //是否是第二次重连上
		this.isReconnecting = false; //是否正在重连中
		this.pingTimer = null; // ping timer
		this.pingTimeoutTimes = 0; // ping-timeout times
	}
	connect(url, token, success, failed) {
		let connectUrl = url;
		let index = url.indexOf('?');
		let query = {};
		if (index !== -1) {
			connectUrl = url.substr(0, index);
			let str = url.substr(index + 1);
			let strs = str.split('&');
			for (var i = 0; i < strs.length; i++) {
				let item = strs[i];
				let sIn = item.indexOf('=');
				if (sIn !== -1) {
					let key = item.substr(0, sIn);
					let value = item.substr(sIn + 1);
					query[key] = value;
				}
			}
		}
		const defaultOptions = {
			reconnect: true,
			secure: false,
			reconnectionAttempts: reconnectionAttempts,
			'force new connection': true,
			transports: ['websocket'],
			reconnectionDelayMax: 5000,
			reconnectionDelay: 1000,
			timeout: 3000,
			randomizationFactor: 0.3,
		};
		if (url.indexOf('https://') == 0) {
			defaultOptions.agent = https.globalAgent;
		}
		Logger.info(`ivcs-js-sdk:agent connect ams options,${JSON.stringify(defaultOptions)}`);
		query.token = token;
		query.servicePath = '_ivcs_ams';
		defaultOptions.query = query;
		let amsUrl;
		if (URL && URL.parse) {
			amsUrl = URL.parse(connectUrl);
			//work for Intranet ams sio
			if (process.env.ivcsAgentMode == 1) {
				https.globalAgent.options.rejectUnauthorized = false;
			}
		} else if (URL && new URL(connectUrl)) {
			amsUrl = new URL(connectUrl);
		} else {
			amsUrl = new UrlParse(connectUrl).getUrlParams();
		}
		if (amsUrl.pathname && amsUrl.pathname !== '/') {
			defaultOptions.path = amsUrl.pathname;
			amsUrl.pathname = '';
			if (URL && URL.format) {
				connectUrl = URL.format(amsUrl);
			} else {
				connectUrl = amsUrl.toString();
			}
		}
		this.socket = io.connect(connectUrl, defaultOptions);
		this.socket.on('connect', data => {
			Logger.info(`ivcs-js-sdk:ams connect at,${new Date().toISOString()}, data:${data}`);
			this.connected = true;
			this.reconnectTimes = 0;
			this.isReconnecting = false;
			success();
			this.emit('onConnectStatus', 'connected');
		});
		this.socket.on('connect_error', data => {
			Logger.info(`ivcs-js-sdk:ams connect_error at,${new Date().toISOString()}, data:${data}`);
			this.emit('onConnectStatus', 'disconnect');
			failed('connect_error');
		});
		this.socket.on('connect_timeout', function (data) {
			Logger.info(`ivcs-js-sdk:ams connect timeout at,${new Date().toISOString()}, data:${data}`);
			failed('connect_timeout');
		});
		//连接失败，判断是否需要重新连接
		this.socket.on('error', function (data) {
			Logger.info(`ivcs-js-sdk:ams connect error at,${new Date().toISOString()}, data:${data}`);
			this.emit('onConnectStatus', 'disconnect');
			failed(data);
		});
		this.socket.on('disconnect', data => {
			Logger.info(`ivcs-js-sdk:ams disconnect at,${new Date().toISOString()}, data:${data}`);
			this.emit('onConnectStatus', 'disconnect');
		});
		this.socket.on('reconnect', function (data) {
			Logger.info(`ivcs-js-sdk:ams reconnect at,${new Date().toISOString()}, data:${data}`);
		});
		this.socket.on('reconnect_attempt', data => {
			//data = 尝试的次数
			Logger.info(`ivcs-js-sdk:ams reconnect_attempt at,${new Date().toISOString()}, data:${data}`);
			this.isReconnecting = true;
		});
		//重连间隔5秒
		this.socket.on('reconnecting', reconnectTimes => {
			Logger.info(`ivcs-js-sdk:ams reconnecting at,${new Date().toISOString()}, times:${reconnectTimes}`);
			this.reconnectTimes++;
			this.emit('onConnectStatus', 'reconnecting');
		});
		this.socket.on('reconnect_error', function (data) {
			Logger.info(`ivcs-js-sdk:ams reconnect_error at,${new Date().toISOString()}, data:${data}`);
			this.connected = false;
		});
		this.socket.on('reconnect_failed', data => {
			Logger.info(`ivcs-js-sdk:ams reconnect_failed at,${new Date().toISOString()}, data:${data}`);
			this.isReconnecting = false;
			this.emit('onConnectStatus', 'reconnect_failed');
			this.emit('onConnectStatus', 'disconnect');
			clearInterval(this.pingTimer);
			this.pingTimer = null;
		});
	}

	onConnectStatus(ack) {
		this.on('onConnectStatus', status => {
			ack(status);
		});
	}

	disconnect() {
		this.socket.disconnect();
	}

	onConnectError(ack) {
		this.receive('connect_error', reason => {
			ack(reason);
		});
		this.receive('error', error => {
			ack(error);
		});
		this.receive('reconnect', time => {
			ack(time);
		});
	}

	onDisconnect(ack) {
		this.receive('disconnect', reason => {
			Logger.info('ivcs-js-sdk: ams disconnect reason:', reason);
			if (this.connected) {
				if (this.reconnectTimes >= reconnectionAttempts) {
					dispatchEvent(AmsEvent.amsSocketIoDisconnected, errorCode.amsSocketIoDisconnected, 'ams socket io disconnected');
					ack(reason);
				}
			}
		});
	}

	onReconnectFailed(ack) {
		this.receive('reconnect_failed', reason => {
			if (this.reconnectTimes >= reconnectionAttempts) {
				dispatchEvent(AmsEvent.amsSocketIoReconnectFailed, errorCode.amsSocketIoReconnectFailed, 'ams socket io reconnect_failed');
				ack(reason);
			}
		});
	}

	receive(eventName, ack) {
		if (this.socket) {
			this.socket.on(eventName, ack);
		} else {
			ack('error', `socket io on event,${eventName} error:there is no socket connect`);
		}
	}

	send(eventName, data, ack) {
		if (eventName === 'private-ping') {
			let isReconnectingInterval = setTimeout(() => {
				this.emit('onConnectStatus', 'ping-timeout');
				if (this.pingTimeoutTimes >= pingTimeoutAttempts) {
					Logger.info(`ivcs-js-sdk:ams private-ping timeout, pingTimeoutTimes:${this.pingTimeoutTimes}.`);
					this.emit('onConnectStatus', 'reconnect_failed');
					clearInterval(this.pingTimer);
					this.pingTimer = null;
					this.pingTimeoutTimes = 0;
					return;
				}
				this.pingTimeoutTimes++;
			}, 2000);
			this.socket.emit(eventName, (status, res) => {
				clearTimeout(isReconnectingInterval);
				isReconnectingInterval = null;
				this.emit('onConnectStatus', 'ping-connected');
				this.pingTimeoutTimes = 0;
				ack(status, res);
			});
			return;
		}
		if (this.isReconnecting) {
			ack('reconnecting', 'ams socketio is reconnecting.');
			return;
		}
		if (!this.connected) {
			Logger.error(`ivcs-js-sdk:ams socketio disconnected with event:${eventName}`);
			ack('reconnecting', `ams socketio disconnected with event:${eventName}`);
		}
		//当刚开始断网一段时间，还未检测到reconnect的时候this.socket.connected=false。
		else if (this.socket && (this.socket.connected || this.connected)) {
			//socketio正在重连中的时候去发送指令，指令会丢失
			// const waitResultTime = 60 * 1000;
			let isReconnectingInterval = setTimeout(() => {
				ack('timeout', `send message with event:${eventName} timeout`);
			}, waitMessageResponseTimes);
			if (data) {
				this.socket.emit(eventName, data, (status, res) => {
					clearTimeout(isReconnectingInterval);
					isReconnectingInterval = null;
					ack(status, res);
				});
			} else {
				this.socket.emit(eventName, (status, res) => {
					clearTimeout(isReconnectingInterval);
					isReconnectingInterval = null;
					ack(status, res);
				});
			}
		} else {
			Logger.error('ivcs-js-sdk:There is no socket connect while send message with Name', eventName, '&and data,', data);
			ack('error', 'There is no socket connect while send message with Name:' + eventName + '&and data:', data);
		}
	}

	onConnect(ack) {
		this.receive('connect', () => {
			this.connected = true;
			this.isReconnecting = false;
			ack();
		});
	}

	login(ack) {
		this.send('agent-login', null, (status, resp) => {
			if (status === 'ok') {
				this.pingTimer = setInterval(() => {
					this.privatePing((status, resp) => {});
				}, 3000);
			}
			ack(status, resp);
		});
	}

	privatePing(ack) {
		this.send('private-ping', null, (status, resp) => {
			ack(status, resp);
		});
	}

	logout(ack) {
		this.send('agent-logout', null, (status, resp) => {
			clearInterval(this.pingTimer);
			this.pingTimer = null;
			this.disconnect();
			ack(status, resp);
		});
	}

	getQueueInfo(ack) {
		this.send('agent-queue-info', null, function (status, resp) {
			ack(status, resp);
		});
	}

	checkIn(groups, ack) {
		this.send('agent-checkIn', groups, function (status, resp) {
			ack(status, resp);
		});
	}

	checkOut(groups, ack) {
		this.send('agent-checkOut', groups, function (status, resp) {
			ack(status, resp);
		});
	}

	refreshTicket(ack) {
		this.send('refresh-ticket', null, function (status, resp) {
			ack(status, resp);
		});
	}

	afterWorkEnd(ack) {
		this.send('agent-afterWork-end', null, function (status, resp) {
			ack(status, resp);
		});
	}

	resumeConnection(ticket, ack) {
		this.send('resume-connection', ticket, (status, resp) => {
			Logger.info(`ivcs-js-sdk:ams resumeConnection status,${status},response:${resp},time:${new Date().toISOString()}`);
			ack(status, resp);
			if (status != 'ok') {
				this.emit('resume-connect-error', resp);
			} else {
				this.emit('resume-connect-success', resp);
			}
		});
	}

	/**
	 * ams resume-connection-error
	 * @param {function} ack
	 */
	resumeConnectionError(ack) {
		this.on('resume-connect-error', reason => {
			ack(reason);
		});
	}

	/**
	 * ams resume-connection-success
	 * @param {function} ack
	 */
	resumeConnectionSuccess(ack) {
		this.on('resume-connect-success', data => {
			ack(data);
		});
	}

	onResetStatus(ack) {
		this.receive('reset-status', data => {
			Logger.info('ivcs-js-sdk:reset status', data);
			ack(data);
		});
	}

	// onAgentAfterWorkEnd (ack) {
	// 	this.receive('agent-afterWork-end', data => {
	// 		ack(data);
	// 	})
	// }

	onRing(ack) {
		this.receive('ring', (data, onResponse) => {
			onResponse('ok');
			Logger.info('ivcs-js-sdk:ring origin data', data);
			let session = {};
			session.sessionId = data.session.id;
			session.url = data.session.url;
			session.userData = data.userData;
			session.bizScenarioId = data.session.bizScenarioId;
			session.group = data.session.group;
			session.room = data.session.room;
			session.media = data?.session?.media;
			session.invitationId = data.invitationId;
			ack(session);
		});
	}

	onStopRinging(ack) {
		this.receive('stop-ringing', data => {
			ack(data);
		});
	}

	onError(ack) {
		this.receive('error', data => {
			ack(data);
		});
	}

	onDrop(ack) {
		this.receive('drop', data => {
			// ["drop",{"reason":"accountDeleted"}]
			ack(data ? data.reason : '');
		});
	}

	onReplaced(ack) {
		this.receive('replaced', () => {
			ack();
		});
	}

	ready(ack) {
		this.send('agent-ready', null, function (status, resp) {
			ack(status, resp);
		});
	}

	unReady(ack) {
		this.send('agent-unready', null, function (status, resp) {
			ack(status, resp);
		});
	}

	answerCall(ack) {
		this.send('agent-answer', null, function (status, resp) {
			ack(status, resp);
		});
	}

	rejectCall(ack) {
		this.send('agent-reject', null, function (status, resp) {
			ack(status, resp);
		});
	}

	hangup(data, ack) {
		this.send('agent-hangup', data, function (status, resp) {
			ack(status, resp);
		});
	}

	appoint(para, ack) {
		this.send('agent-appoint', para, function (status, resp) {
			ack(status, resp);
		});
	}

	otherWork(data, ack) {
		this.send('agent-otherWork', data, function (status, resp) {
			ack(status, resp);
		});
	}
	setOtherWorkReason(data, ack) {
		this.send('set-otherwork-reason', data, function (status, resp) {
			ack(status, resp);
		});
	}
}

export { AMSClient, AmsEvent };

/**
 * 坐席操作事件，如登入，登出，就绪等
 * @param {amsClient} amsClient
 * @param {string} eventName 'login' | 'logout' | 'checkIn' | 'checkOut' | 'ready' | 'unReady' | 'appoint' | 'otherWork' | 'setOtherWorkReason' | 'afterWorkEnd'
 * @param {function} onSuccess callback成功
 * @param {function} onFailure 失败{code,message}, '3001'='ams未实例化', '3006'='ams sendMessage error', '3008'='ams sendMessage timeout', '3009'='ams sendMessage reconnecting'
 * @param {object} params 参数
 * @param {function} onFinish 执行结束
 * @returns
 */
export function operateEvent(amsClient, eventName, onSuccess, onFailure, params, onFinish) {
	let isValid = validateAmsClient(amsClient);
	if (isValid != 'ok') {
		onFailure && onFailure(isValid);
		return;
	}
	function handleResponse(eventName, s, r) {
		if (s === 'ok') {
			onSuccess && onSuccess(r);
		} else if (s === 'timeout') {
			Logger.info(`ivcs-js-sdk:ams send event:${eventName}, error: timeout, response: `, r, new Date().toISOString());
			onFailure && onFailure(new IvcsError(ivcsErrorCodeEnum.ERROR_AMS_MESSAGE_TIMEOUT, r));
		} else if (s === 'reconnecting') {
			Logger.info(`ivcs-js-sdk:ams send event:${eventName}, error: reconnecting, response: `, r, new Date().toISOString());
			onFailure && onFailure(new IvcsError(ivcsErrorCodeEnum.ERROR_AMS_RECONNECTING, r));
		} else {
			Logger.info(`ivcs-js-sdk:ams send event:${eventName}, error, response: `, r, new Date().toISOString());
			onFailure && onFailure(new IvcsError(ivcsErrorCodeEnum.ERROR_AMS_API_FAILED, r));
		}
		onFinish && onFinish();
	}
	if (params) {
		amsClient[eventName](params, (s, r) => {
			handleResponse(eventName, s, r);
		});
	} else {
		amsClient[eventName]((s, r) => {
			handleResponse(eventName, s, r);
		});
	}
}

export function refreshAmsTicket(amsClient, refreshTicketTime, ticketInvalidAck) {
	let intervalMax = Math.pow(2, 31) - 1;
	let refresh_ticket_time = refreshTicketTime > intervalMax ? intervalMax : refreshTicketTime;
	refresh_ticket_time < 100 ? (refresh_ticket_time = 100) : undefined;
	amsClient.refreshTicketInterval = setInterval(() => {
		operateEvent(
			amsClient,
			'refreshTicket',
			refreshResp => {
				amsClient.ticket = refreshResp.ticket;
			},
			error => {
				if (error && error.code === ivcsErrorCodeEnum.ERROR_AMS_API_FAILED) {
					clearInterval(amsClient.refreshTicketInterval);
					amsClient.refreshTicketInterval = null;
					dispatchEvent(AmsEvent.refreshTicketFailed, errorCode.refreshTicketFailed, 'ams sio refreshTicketFailed');
					ticketInvalidAck(error);
				}
				// 其他错误不影响该操作
			}
		);
	}, refresh_ticket_time);
}

export function resumeAmsConnection(amsClient, ticket) {
	if (ticket) {
		operateEvent(
			amsClient,
			'resumeConnection',
			resumeResp => {
				amsClient.ticket = resumeResp.ticket;
				refreshAmsTicket(amsClient, amsClient.refersh_ticket_time, () => {});
			},
			error => {
				if (error && error.code === ivcsErrorCodeEnum.ERROR_AMS_API_FAILED) {
					dispatchEvent(AmsEvent.resumeConnectionFailed, errorCode.resumeConnectionFailed, error);
				}
			},
			ticket
		);
	} else {
		Logger.debug(`ivcs-js-sdk:resumeAmsConnection:ticket is empty,ams not login don't need to resume ams connection,at:${new Date().toISOString()}`);
	}
}

export function releaseAmsClient(amsClient) {
	if (amsClient) {
		amsClient.accessToken = null;
		amsClient.connected = false;
		amsClient.ticket = null;
		amsClient.disconnect();
		clearInterval(amsClient.refreshTicketInterval);
		amsClient.refreshTicketInterval = null;
		Logger.info(`ivcs-js-sdk:release ams client,at:${new Date().toISOString()}}`);
	}
}

export function validateAmsClient(amsClient) {
	if (!amsClient) {
		Logger.info('ivcs-js-sdk:validateAmsClient error: amsClient is undefined');
		return new IvcsError(ivcsErrorCodeEnum.ERROR_AMS_UNDEFINED, 'ams client is undefined');
	}
	return 'ok';
}

export async function answerCall(amsClient) {
	return await new Promise((resolve, reject) => {
		operateEvent(
			amsClient,
			'answerCall',
			() => {
				resolve();
			},
			error => {
				reject(error);
			}
		);
	});
}

export async function hangupCall(amsClient, data) {
	return await new Promise((resolve, reject) => {
		operateEvent(
			amsClient,
			'hangup',
			s => {
				resolve(s);
			},
			error => {
				if (error && error.code === ivcsErrorCodeEnum.ERROR_AMS_API_FAILED) {
					reject(error);
				} else {
					resolve(error);
				}
			},
			data
		);
	});
}

export function amsErrorEvent(callback) {
	let resume = addEventListener(AmsEvent.resumeConnectionFailed, info => {
		removeEventListener(AmsEvent.resumeConnectionFailed, resume);
		callback(info);
	});
	let disconnect = addEventListener(AmsEvent.amsSocketIoDisconnected, info => {
		removeEventListener(AmsEvent.amsSocketIoDisconnected, disconnect);
		callback(info);
	});
	let reconnectFailed = addEventListener(AmsEvent.socketIoReconnectFailed, info => {
		removeEventListener(AmsEvent.socketIoReconnectFailed, reconnectFailed);
		callback(info);
	});
}

export async function connectAmsService(amsClient, amsUrl, accessToken, onConnectError) {
	return await new Promise((resolve, reject) => {
		amsClient.connect(
			amsUrl,
			accessToken,
			() => {},
			reason => {
				let error = new IvcsError(ivcsErrorCodeEnum.ERROR_AMS_CONNECT_FAILED, 'ams connect error:' + reason);
				reject(error);
			}
		);
		amsClient.onConnect(resp => {
			amsClient.accessToken = accessToken;
			amsClient.connected = true;
			if (!amsClient.isReconnected) {
				amsClient.isReconnected = true;
				resolve(resp);
			} else {
				//remove refresh ticket timing task
				if (amsClient.refreshTicketInterval) {
					Logger.debug('ivcs-js-sdk:remove refresh ticket timing task while resume ams connect');
					clearInterval(amsClient.refreshTicketInterval);
					amsClient.refreshTicketInterval = null;
				}
				resumeAmsConnection(amsClient, amsClient.ticket);
			}
		});
		amsClient.onDisconnect(reason => {
			Logger.warning('ivcs-js-sdk:ams socket is disconnected,', reason, new Date(), `at:${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}.${new Date().getMilliseconds()}`);
			amsClient.connected = false;
			//remove refresh ticket timing task
			if (amsClient.refreshTicketInterval) {
				Logger.info('ivcs-js-sdk:remove refresh ticket timing task with reason ', reason);
				clearInterval(amsClient.refreshTicketInterval);
				amsClient.refreshTicketInterval = null;
			}
		});
		amsClient.onReconnectFailed(reason => {
			Logger.warning('ivcs-js-sdk:ams socket reconnect failed,', reason, new Date(), `at:${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}.${new Date().getMilliseconds()}`);
			amsClient.connected = false;
			//remove refresh ticket timing task
			if (amsClient.refreshTicketInterval) {
				Logger.info('ivcs-js-sdk:remove refresh ticket timing task with reason ', reason);
				clearInterval(amsClient.refreshTicketInterval);
				amsClient.refreshTicketInterval = null;
			}
			onConnectError && onConnectError(reason);
		});
		amsClient.onConnectError(reason => {
			Logger.warning('ivcs-js-sdk:ams socket is connect-error,', reason, new Date(), `at:${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()}.${new Date().getMilliseconds()}`);
			//remove refresh ticket timing task
			if (amsClient.refreshTicketInterval) {
				Logger.info('ivcs-js-sdk:remove refresh ticket timing task with reason', reason);
				clearInterval(amsClient.refreshTicketInterval);
				amsClient.refreshTicketInterval = null;
			}
		});
	});
}
